From f57afe51af0d91c1a8358126a0813fb99091ccab Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 21 Oct 2019 04:11:24 +0200 Subject: [PATCH] gridview: Add move keybindings --- gtk/gtkgridview.c | 278 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 277 insertions(+), 1 deletion(-) diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index a3d7a518c2..1800f12df5 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -1455,6 +1455,265 @@ gtk_grid_view_activate_item (GtkWidget *widget, g_signal_emit (widget, signals[ACTIVATE], 0, pos); } +static void +gtk_grid_view_move_to (GtkGridView *self, + guint pos, + gboolean select, + gboolean modify, + gboolean extend) +{ + Cell *cell; + + cell = gtk_list_item_manager_get_nth (self->item_manager, pos, NULL); + if (cell == NULL) + return; + + if (!cell->parent.widget) + { + GtkListItemTracker *tracker = gtk_list_item_tracker_new (self->item_manager); + + /* We need a tracker here to create the widget. + * That needs to have happened or we can't grab it. + * And we can't use a different tracker, because they manage important rows, + * so we create a temporary one. */ + gtk_list_item_tracker_set_position (self->item_manager, tracker, pos, 0, 0); + + cell = gtk_list_item_manager_get_nth (self->item_manager, pos, NULL); + g_assert (cell->parent.widget); + + if (!gtk_widget_grab_focus (cell->parent.widget)) + return; /* FIXME: What now? Can this even happen? */ + + gtk_list_item_tracker_free (self->item_manager, tracker); + } + else + { + if (!gtk_widget_grab_focus (cell->parent.widget)) + return; /* FIXME: What now? Can this even happen? */ + } + + if (select) + gtk_grid_view_select_item (self, pos, modify, extend); +} + +static gboolean +gtk_grid_view_move_cursor (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkGridView *self = GTK_GRID_VIEW (widget); + int amount; + guint orientation; + guint pos, n_items; + gboolean select, modify, extend; + + g_variant_get (args, "(ubbbi)", &orientation, &select, &modify, &extend, &amount); + + if (self->orientation == orientation) + amount *= self->n_columns; + + if (orientation == GTK_ORIENTATION_HORIZONTAL && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + amount = -amount; + + pos = gtk_list_item_tracker_get_position (self->item_manager, self->focus); + n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + if (pos >= n_items || /* no focused item */ + (amount < 0 && pos < -amount)) + return TRUE; + if (amount > 0 && amount > n_items - pos) + { + /* pressing down with no item below the current item is more complicated + * because we want to move to the last row if we're not there yet */ + if (pos / self->n_columns < (n_items - 1) / self->n_columns) + amount = n_items - pos - 1; + else + return TRUE; + } + + gtk_grid_view_move_to (self, pos + amount, select, modify, extend); + + return TRUE; +} + +static gboolean +gtk_grid_view_move_cursor_to_start (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkGridView *self = GTK_GRID_VIEW (widget); + gboolean select, modify, extend; + + if (self->model == NULL || g_list_model_get_n_items (self->model) == 0) + return TRUE; + + g_variant_get (args, "(bbb)", &select, &modify, &extend); + + gtk_grid_view_move_to (self, 0, select, modify, extend); + + return TRUE; +} + +static gboolean +gtk_grid_view_move_cursor_to_end (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkGridView *self = GTK_GRID_VIEW (widget); + gboolean select, modify, extend; + guint n_items; + + if (self->model == NULL) + return TRUE; + + n_items = g_list_model_get_n_items (self->model); + if (n_items == 0) + return TRUE; + + g_variant_get (args, "(bbb)", &select, &modify, &extend); + + gtk_grid_view_move_to (self, n_items - 1, select, modify, extend); + + return TRUE; +} + +static gboolean +gtk_grid_view_move_cursor_page_up (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkGridView *self = GTK_GRID_VIEW (widget); + gboolean select, modify, extend; + int offset, start, size, page_size; + guint pos, new_pos; + + pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor); + if (pos < self->n_columns) /* already on first row */ + return TRUE; + if (!gtk_grid_view_get_size_at_position (self, pos, &start, &size)) + return TRUE; + page_size = gtk_adjustment_get_page_size(self->adjustment[self->orientation]); + if (!gtk_grid_view_get_cell_at_y (self, + MAX (0, start + size - page_size), + &new_pos, + &offset, + NULL)) + return TRUE; + /* gtk_grid_view_get_cell_at_y() returns first column positions, we want to keep columns */ + new_pos += pos % self->n_columns; + if (offset > 0) + new_pos += self->n_columns; + if (new_pos >= pos) + new_pos = pos - self->n_columns; + + g_variant_get (args, "(bbb)", &select, &modify, &extend); + + gtk_grid_view_move_to (self, new_pos, select, modify, extend); + + return TRUE; +} + +static gboolean +gtk_grid_view_move_cursor_page_down (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkGridView *self = GTK_GRID_VIEW (widget); + gboolean select, modify, extend; + int start, page_size; + guint pos, new_pos, n_items; + + pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor); + n_items = g_list_model_get_n_items (self->model); + if (n_items == 0 || pos / self->n_columns >= (n_items - 1) / self->n_columns) + return TRUE; + if (!gtk_grid_view_get_size_at_position (self, pos, &start, NULL)) + return TRUE; + page_size = gtk_adjustment_get_page_size(self->adjustment[self->orientation]); + if (gtk_grid_view_get_cell_at_y (self, + start + page_size, + &new_pos, + NULL, NULL)) + { + /* We want a fully visible row and we just checked for the row that covers the + * pixels more than a page down */ + if (new_pos >= self->n_columns) + new_pos -= self->n_columns; + } + else + { + /* scroll to last row if there's nothing in the place we checked */ + new_pos = (n_items - 1); + new_pos -= new_pos % self->n_columns; + } + + /* gtk_grid_view_get_cell_at_y() returns first column positions, we want to keep columns */ + new_pos += pos % self->n_columns; + /* We want to scroll at least one row */ + if (new_pos <= pos) + new_pos = pos + self->n_columns; + /* And finally, we need to check we've not scrolled to a cell in the last row that isn't + * covered because n_items is not a multiple of n_columns */ + if (new_pos >= n_items) + new_pos = n_items - 1; + + g_variant_get (args, "(bbb)", &select, &modify, &extend); + + gtk_grid_view_move_to (self, new_pos, select, modify, extend); + + return TRUE; +} + +static void +gtk_grid_view_add_custom_move_binding (GtkWidgetClass *widget_class, + guint keyval, + GtkShortcutFunc callback) +{ + gtk_widget_class_add_binding (widget_class, + keyval, + 0, + callback, + "(bbb)", TRUE, FALSE, FALSE); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_CONTROL_MASK, + callback, + "(bbb)", FALSE, FALSE, FALSE); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_SHIFT_MASK, + callback, + "(bbb)", TRUE, FALSE, TRUE); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_CONTROL_MASK | GDK_SHIFT_MASK, + callback, + "(bbb)", TRUE, TRUE, TRUE); +} + +static void +gtk_grid_view_add_move_binding (GtkWidgetClass *widget_class, + guint keyval, + GtkOrientation orientation, + int amount) +{ + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_CONTROL_MASK, + gtk_grid_view_move_cursor, + "(ubbbi)", orientation, FALSE, FALSE, FALSE, amount); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_SHIFT_MASK, + gtk_grid_view_move_cursor, + "(ubbbi)", orientation, TRUE, FALSE, TRUE, amount); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_CONTROL_MASK | GDK_SHIFT_MASK, + gtk_grid_view_move_cursor, + "(ubbbi)", orientation, TRUE, TRUE, TRUE, amount); +} + static void gtk_grid_view_class_init (GtkGridViewClass *klass) { @@ -1646,9 +1905,26 @@ gtk_grid_view_class_init (GtkGridViewClass *klass) "u", gtk_grid_view_scroll_to_item); + gtk_grid_view_add_move_binding (widget_class, GDK_KEY_Up, GTK_ORIENTATION_VERTICAL, -1); + gtk_grid_view_add_move_binding (widget_class, GDK_KEY_KP_Up, GTK_ORIENTATION_VERTICAL, -1); + gtk_grid_view_add_move_binding (widget_class, GDK_KEY_Down, GTK_ORIENTATION_VERTICAL, 1); + gtk_grid_view_add_move_binding (widget_class, GDK_KEY_KP_Down, GTK_ORIENTATION_VERTICAL, 1); + gtk_grid_view_add_move_binding (widget_class, GDK_KEY_Left, GTK_ORIENTATION_HORIZONTAL, -1); + gtk_grid_view_add_move_binding (widget_class, GDK_KEY_KP_Left, GTK_ORIENTATION_HORIZONTAL, -1); + gtk_grid_view_add_move_binding (widget_class, GDK_KEY_Right, GTK_ORIENTATION_HORIZONTAL, 1); + gtk_grid_view_add_move_binding (widget_class, GDK_KEY_KP_Right, GTK_ORIENTATION_HORIZONTAL, 1); + + gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_Home, gtk_grid_view_move_cursor_to_start); + gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Home, gtk_grid_view_move_cursor_to_start); + gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_End, gtk_grid_view_move_cursor_to_end); + gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_KP_End, gtk_grid_view_move_cursor_to_end); + gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_Page_Up, gtk_grid_view_move_cursor_page_up); + gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Up, gtk_grid_view_move_cursor_page_up); + gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_Page_Down, gtk_grid_view_move_cursor_page_down); + gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Down, gtk_grid_view_move_cursor_page_down); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_a, GDK_CONTROL_MASK, "list.select-all", NULL); gtk_widget_class_add_binding_action (widget_class, GDK_KEY_slash, GDK_CONTROL_MASK, "list.select-all", NULL); - gtk_widget_class_add_binding_action (widget_class, GDK_KEY_A, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "list.unselect-all", NULL); gtk_widget_class_add_binding_action (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, "list.unselect-all", NULL); -- 2.30.2